package class08;

public class Code02_MergeSort {

	// 递归方法实现
	public static void mergeSort1(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		process(arr, 0, arr.length - 1);
	}

	// 递归版本
	// arr[L...R]范围上，请让这个范围上的数，有序！
	public static void process(int[] arr, int L, int R) {
		if (L == R) {
			//只有一个数, 不需要排序
			return;
		}
		//计算中点, 防止int溢出
		// int mid = (L + R) / 2
		int mid = L + ((R - L) >> 1);
		process(arr, L, mid);
		process(arr, mid + 1, R);
		merge(arr, L, mid, R);
	}

	public static void merge(int[] arr, int L, int M, int R) {
		int[] help = new int[R - L + 1];
		int i = 0;
		//第一段: L - M
		int p1 = L;
		//第二段: M+1 - R
		int p2 = M + 1;
		//两个数组均未越界情况下
		while (p1 <= M && p2 <= R) {
			help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
		}
		// 要么p1越界，要么p2越界
		// 不可能出现：共同越界
		// 补全剩下的有序大数值的数据
		while (p1 <= M) {
			help[i++] = arr[p1++];
		}
		while (p2 <= R) {
			help[i++] = arr[p2++];
		}
		//使用help覆盖 arr
		for (i = 0; i < help.length; i++) {
			arr[L + i] = help[i];
		}
	}

	// 非递归版本
	// 思路: 递归是 N/2 N/4 ... 1, 不断申请新的子计算结果然后汇总
	// 那么模拟递归就可以: 1 2 4 ... N/4 N/2 , 直接手动划分细分好的子计算,然后合并结果
	public static void mergeSort2(int[] arr) {
		//不需要排序的数组
		if (arr == null || arr.length < 2) {
			return;
		}
		//步长为1, 进行merge
		//再每次步长*2, 进行merge
		//步长超过数组长度则停止merge
		int step = 1;
		int N = arr.length;
		//步长小于数组总长度才需要merge
		while (step < N) {
			int L = 0;
			while (L < N) {
				int M = 0;
				//先定位第一个步长的子数组左右边界
				// N - L >= step 防止 L + step - 1 越界(N-1 > L + step - 1)
				if (N - L >= step) {
					M = L + step - 1;
				} else {
					M = N - 1;
				}
				//仅剩下一个数组了, 不需要merge
				if (M == N - 1) {
					break;
				}
				//再定位第二个步长的子数组左右边界
				int R = 0;
				if (N - 1 - M >= step) {
					R = M + step;
				} else {
					R = N - 1;
				}
				//对同步长的第一个和第二个子数组的进行merge操作
				merge(arr, L, M, R);
				if (R == N - 1) {
					//最后两个数组合并完了, 不需要继续合并了
					break;
				} else {
					//重新定位L, 以便继续合并第三,四个子数组...
					//如此循环下去
					L = R + 1;
				}
			}
			//防止 step*2 溢出, 数组长度超过最大整数的一半
			//需要>, /2是向下取整, 17个数, 17/2=8, 实际需要merge到16, 不能在8处停止
			if (step > N / 2) {
				break;
			}
			step *= 2;
		}

	}

	// 时间复杂度: O(logN * N), 需要logN次操作, 每次操作调整了N个数
	// 非递归方法实现
	// 精简写法
	public static void mergeSort3(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		int N = arr.length;
		int mergeSize = 1;
		while (mergeSize < N) {
			int L = 0;
			while (L < N) {
				if (mergeSize >= N - L) {
					break;
				}
				int M = L + mergeSize - 1;
				int R = M + Math.min(mergeSize, N - M - 1);
				merge(arr, L, M, R);
				L = R + 1;
			}
			if (mergeSize > N / 2) {
				break;
			}
			mergeSize <<= 1;
		}
	}

	// for test
	public static int[] generateRandomArray(int maxSize, int maxValue) {
		int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
		for (int i = 0; i < arr.length; i++) {
			arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
		}
		return arr;
	}

	// for test
	public static int[] copyArray(int[] arr) {
		if (arr == null) {
			return null;
		}
		int[] res = new int[arr.length];
		for (int i = 0; i < arr.length; i++) {
			res[i] = arr[i];
		}
		return res;
	}

	// for test
	public static boolean isEqual(int[] arr1, int[] arr2) {
		if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
			return false;
		}
		if (arr1 == null && arr2 == null) {
			return true;
		}
		if (arr1.length != arr2.length) {
			return false;
		}
		for (int i = 0; i < arr1.length; i++) {
			if (arr1[i] != arr2[i]) {
				return false;
			}
		}
		return true;
	}

	// for test
	public static void printArray(int[] arr) {
		if (arr == null) {
			return;
		}
		for (int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}

	// for test
	public static void main(String[] args) {
		int testTime = 500000;
		int maxSize = 100;
		int maxValue = 100;
		System.out.println("测试开始");
		for (int i = 0; i < testTime; i++) {
			int[] arr1 = generateRandomArray(maxSize, maxValue);
			int[] arr2 = copyArray(arr1);
			mergeSort1(arr1);
			mergeSort2(arr2);
			if (!isEqual(arr1, arr2)) {
				System.out.println("出错了！");
				printArray(arr1);
				printArray(arr2);
				break;
			}
		}
		System.out.println("测试结束");
	}

}
